/*
 * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
 * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

using System;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using System.Collections.Generic;
using System.Linq;

namespace QuantConnect.Tests.Common.Data
{
    [TestFixture]
    public class RenkoConsolidatorTests: BaseConsolidatorTests
    {
        [TestCase(0)]
        [TestCase(-1)]
        public void WickedRenkoConsolidatorFailsWhenBarSizeIsZero(double barSize)
        {
            var message = Assert.Throws<ArgumentException>( () => new RenkoConsolidator((decimal)barSize));
            Assert.AreEqual("Renko consolidator BarSize must be strictly greater than zero", message.Message);
        }

        [Test]
        public void WickedOutputTypeIsRenkoBar()
        {
            using var consolidator = new RenkoConsolidator(10.0m);

            Assert.AreEqual(typeof(RenkoBar), consolidator.OutputType);
        }

        [Test]
        public void WickedNoFallingRenko()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 9.1m));

            Assert.AreEqual(renkos.Count, 0);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 10.0m);
            Assert.AreEqual(openRenko.Low, 9.1m);
            Assert.AreEqual(openRenko.Close, 9.1m);
        }

        [Test]
        public void WickedNoRisingRenko()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 10.9m));

            Assert.AreEqual(renkos.Count, 0);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 10.9m);
            Assert.AreEqual(openRenko.Low, 10.0m);
            Assert.AreEqual(openRenko.Close, 10.9m);
        }

        [Test]
        public void WickedNoFallingRenkoKissLimit()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 9.0m));

            Assert.AreEqual(renkos.Count, 0);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 10.0m);
            Assert.AreEqual(openRenko.Low, 9.0m);
            Assert.AreEqual(openRenko.Close, 9.0m);
        }

        [Test]
        public void WickedNoRisingRenkoKissLimit()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 11.0m));

            Assert.AreEqual(renkos.Count, 0);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 11.0m);
            Assert.AreEqual(openRenko.Low, 10.0m);
            Assert.AreEqual(openRenko.Close, 11.0m);
        }

        [Test]
        public void WickedOneFallingRenko()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 8.9m));

            Assert.AreEqual(renkos.Count, 1);

            Assert.AreEqual(renkos[0].Open, 10.0m);
            Assert.AreEqual(renkos[0].High, 10.0m);
            Assert.AreEqual(renkos[0].Low, 9.0m);
            Assert.AreEqual(renkos[0].Close, 9.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[0].Spread, 1.0m);
            Assert.AreEqual(renkos[0].Start, tickOn1);
            Assert.AreEqual(renkos[0].EndTime, tickOn2);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Start, tickOn2);
            Assert.AreEqual(openRenko.EndTime, tickOn2);
            Assert.AreEqual(openRenko.Open, 9.0m);
            Assert.AreEqual(openRenko.High, 9.0m);
            Assert.AreEqual(openRenko.Low, 8.9m);
            Assert.AreEqual(openRenko.Close, 8.9m);
        }

        [Test]
        public void WickedOneRisingRenko()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 10.1m));

            Assert.AreEqual(renkos.Count, 1);

            Assert.AreEqual(renkos[0].Open, 9.0m);
            Assert.AreEqual(renkos[0].High, 10.0m);
            Assert.AreEqual(renkos[0].Low, 9.0m);
            Assert.AreEqual(renkos[0].Close, 10.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[0].Spread, 1.0m);
            Assert.AreEqual(renkos[0].Start, tickOn1);
            Assert.AreEqual(renkos[0].EndTime, tickOn2);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Start, tickOn2);
            Assert.AreEqual(openRenko.EndTime, tickOn2);
            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 10.1m);
            Assert.AreEqual(openRenko.Low, 10.0m);
            Assert.AreEqual(openRenko.Close, 10.1m);
        }

        [Test]
        public void WickedTwoFallingThenOneRisingRenkos()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 8.9m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.1m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 7.8m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 7.6m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.1m));

            Assert.AreEqual(renkos.Count, 3);

            Assert.AreEqual(renkos[0].Open, 10.0m);
            Assert.AreEqual(renkos[0].High, 10.5m);
            Assert.AreEqual(renkos[0].Low, 9.0m);
            Assert.AreEqual(renkos[0].Close, 9.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[0].Spread, 1.0m);

            Assert.AreEqual(renkos[1].Open, 9.0m);
            Assert.AreEqual(renkos[1].High, 9.2m);
            Assert.AreEqual(renkos[1].Low, 8.0m);
            Assert.AreEqual(renkos[1].Close, 8.0m);
            Assert.AreEqual(renkos[1].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[1].Spread, 1.0m);

            Assert.AreEqual(renkos[2].Open, 9.0m);
            Assert.AreEqual(renkos[2].High, 10.0m);
            Assert.AreEqual(renkos[2].Low, 7.6m);
            Assert.AreEqual(renkos[2].Close, 10.0m);
            Assert.AreEqual(renkos[2].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[2].Spread, 1.0m);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 10.1m);
            Assert.AreEqual(openRenko.Low, 10.0m);
            Assert.AreEqual(openRenko.Close, 10.1m);
        }

        [Test]
        public void WickedTwoRisingThenOneFallingRenkos()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.6m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.1m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.7m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.6m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.4m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.9m));

            Assert.AreEqual(renkos.Count, 3);

            Assert.AreEqual(renkos[0].Open, 10.0m);
            Assert.AreEqual(renkos[0].High, 11.0m);
            Assert.AreEqual(renkos[0].Low, 9.6m);
            Assert.AreEqual(renkos[0].Close, 11.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[0].Spread, 1.0m);

            Assert.AreEqual(renkos[1].Open, 11.0m);
            Assert.AreEqual(renkos[1].High, 12.0m);
            Assert.AreEqual(renkos[1].Low, 10.7m);
            Assert.AreEqual(renkos[1].Close, 12.0m);
            Assert.AreEqual(renkos[1].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[1].Spread, 1.0m);

            Assert.AreEqual(renkos[2].Open, 11.0m);
            Assert.AreEqual(renkos[2].High, 12.4m);
            Assert.AreEqual(renkos[2].Low, 10.0m);
            Assert.AreEqual(renkos[2].Close, 10.0m);
            Assert.AreEqual(renkos[2].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[2].Spread, 1.0m);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 10.0m);
            Assert.AreEqual(openRenko.High, 10.0m);
            Assert.AreEqual(openRenko.Low, 9.9m);
            Assert.AreEqual(openRenko.Close, 9.9m);
        }

        [Test]
        public void WickedThreeRisingGapRenkos()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 14.0m));

            Assert.AreEqual(renkos.Count, 3);

            Assert.AreEqual(renkos[0].Start, tickOn1);
            Assert.AreEqual(renkos[0].EndTime, tickOn2);
            Assert.AreEqual(renkos[0].Open, 10.0m);
            Assert.AreEqual(renkos[0].High, 11.0m);
            Assert.AreEqual(renkos[0].Low, 10.0m);
            Assert.AreEqual(renkos[0].Close, 11.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[0].Spread, 1.0m);

            Assert.AreEqual(renkos[1].Start, tickOn2);
            Assert.AreEqual(renkos[1].EndTime, tickOn2);
            Assert.AreEqual(renkos[1].Open, 11.0m);
            Assert.AreEqual(renkos[1].High, 12.0m);
            Assert.AreEqual(renkos[1].Low, 11.0m);
            Assert.AreEqual(renkos[1].Close, 12.0m);
            Assert.AreEqual(renkos[1].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[1].Spread, 1.0m);

            Assert.AreEqual(renkos[2].Start, tickOn2);
            Assert.AreEqual(renkos[2].EndTime, tickOn2);
            Assert.AreEqual(renkos[2].Open, 12.0m);
            Assert.AreEqual(renkos[2].High, 13.0m);
            Assert.AreEqual(renkos[2].Low, 12.0m);
            Assert.AreEqual(renkos[2].Close, 13.0m);
            Assert.AreEqual(renkos[2].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[2].Spread, 1.0m);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Start, tickOn2);
            Assert.AreEqual(openRenko.EndTime, tickOn2);
            Assert.AreEqual(openRenko.Open, 13.0m);
            Assert.AreEqual(openRenko.High, 14.0m);
            Assert.AreEqual(openRenko.Low, 13.0m);
            Assert.AreEqual(openRenko.Close, 14.0m);
        }

        [Test]
        public void WickedThreeFallingGapRenkos()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 14.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn2, 10.0m));

            Assert.AreEqual(renkos.Count, 3);

            Assert.AreEqual(renkos[0].Start, tickOn1);
            Assert.AreEqual(renkos[0].EndTime, tickOn2);
            Assert.AreEqual(renkos[0].Open, 14.0m);
            Assert.AreEqual(renkos[0].High, 14.0m);
            Assert.AreEqual(renkos[0].Low, 13.0m);
            Assert.AreEqual(renkos[0].Close, 13.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[0].Spread, 1.0m);

            Assert.AreEqual(renkos[1].Start, tickOn2);
            Assert.AreEqual(renkos[1].EndTime, tickOn2);
            Assert.AreEqual(renkos[1].Open, 13.0m);
            Assert.AreEqual(renkos[1].High, 13.0m);
            Assert.AreEqual(renkos[1].Low, 12.0m);
            Assert.AreEqual(renkos[1].Close, 12.0m);
            Assert.AreEqual(renkos[1].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[1].Spread, 1.0);

            Assert.AreEqual(renkos[2].Start, tickOn2);
            Assert.AreEqual(renkos[2].EndTime, tickOn2);
            Assert.AreEqual(renkos[2].Open, 12.0m);
            Assert.AreEqual(renkos[2].High, 12.0m);
            Assert.AreEqual(renkos[2].Low, 11.0m);
            Assert.AreEqual(renkos[2].Close, 11.0m);
            Assert.AreEqual(renkos[2].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[2].Spread, 1.0m);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 11.0m);
            Assert.AreEqual(openRenko.High, 11.0m);
            Assert.AreEqual(openRenko.Low, 10.0m);
            Assert.AreEqual(openRenko.Close, 10.0m);
        }

        [Test]
        public void WickedTwoFallingThenThreeRisingGapRenkos()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 8.9m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.1m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 7.8m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 7.6m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 8.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.2m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.1m));

            Assert.AreEqual(renkos.Count, 5);

            Assert.AreEqual(renkos[0].Open, 10.0m);
            Assert.AreEqual(renkos[0].High, 10.5m);
            Assert.AreEqual(renkos[0].Low, 9.0m);
            Assert.AreEqual(renkos[0].Close, 9.0m);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[0].Spread, 1.0m);

            Assert.AreEqual(renkos[1].Open, 9.0m);
            Assert.AreEqual(renkos[1].High, 9.2m);
            Assert.AreEqual(renkos[1].Low, 8.0m);
            Assert.AreEqual(renkos[1].Close, 8.0m);
            Assert.AreEqual(renkos[1].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[1].Spread, 1.0m);

            Assert.AreEqual(renkos[2].Open, 9.0m);
            Assert.AreEqual(renkos[2].High, 10.0m);
            Assert.AreEqual(renkos[2].Low, 7.6m);
            Assert.AreEqual(renkos[2].Close, 10.0m);
            Assert.AreEqual(renkos[2].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[2].Spread, 1.0m);

            Assert.AreEqual(renkos[3].Open, 10.0m);
            Assert.AreEqual(renkos[3].High, 11.0m);
            Assert.AreEqual(renkos[3].Low, 10.0m);
            Assert.AreEqual(renkos[3].Close, 11.0m);
            Assert.AreEqual(renkos[3].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[3].Spread, 1.0m);

            Assert.AreEqual(renkos[4].Open, 11.0m);
            Assert.AreEqual(renkos[4].High, 12.0m);
            Assert.AreEqual(renkos[4].Low, 11.0m);
            Assert.AreEqual(renkos[4].Close, 12.0m);
            Assert.AreEqual(renkos[4].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[4].Spread, 1.0m);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 12.0m);
            Assert.AreEqual(openRenko.High, 12.1m);
            Assert.AreEqual(openRenko.Low, 12.0m);
            Assert.AreEqual(openRenko.Close, 12.1m);
        }

        [Test]
        public void WickedTwoRisingThenThreeFallingGapRenkos()
        {
            using var consolidator = new TestRenkoConsolidator(1.0m);

            var renkos = new List<RenkoBar>();

            consolidator.DataConsolidated += (sender, renko) =>
                renkos.Add(renko);

            var tickOn1 = new DateTime(2016, 1, 1, 17, 0, 0, 0);
            var tickOn2 = new DateTime(2016, 1, 1, 17, 0, 0, 1);

            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 9.6m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.1m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.0m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 10.7m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.6m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));

            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.3m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 12.4m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 11.5m));
            consolidator.Update(new IndicatorDataPoint(tickOn1, 7.9m));

            Assert.AreEqual(renkos.Count, 5);

            Assert.AreEqual(renkos[0].Open, 10.0);
            Assert.AreEqual(renkos[0].High, 11.0);
            Assert.AreEqual(renkos[0].Low, 9.6);
            Assert.AreEqual(renkos[0].Close, 11.0);
            Assert.AreEqual(renkos[0].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[0].Spread, 1.0);

            Assert.AreEqual(renkos[1].Open, 11.0);
            Assert.AreEqual(renkos[1].High, 12.0);
            Assert.AreEqual(renkos[1].Low, 10.7);
            Assert.AreEqual(renkos[1].Close, 12.0);
            Assert.AreEqual(renkos[1].Direction, BarDirection.Rising);
            Assert.AreEqual(renkos[1].Spread, 1.0);

            Assert.AreEqual(renkos[2].Open, 11.0);
            Assert.AreEqual(renkos[2].High, 12.4);
            Assert.AreEqual(renkos[2].Low, 10.0);
            Assert.AreEqual(renkos[2].Close, 10.0);
            Assert.AreEqual(renkos[2].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[2].Spread, 1.0);

            Assert.AreEqual(renkos[3].Open, 10.0);
            Assert.AreEqual(renkos[3].High, 10.0);
            Assert.AreEqual(renkos[3].Low, 9.0);
            Assert.AreEqual(renkos[3].Close, 9.0);
            Assert.AreEqual(renkos[3].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[3].Spread, 1.0);

            Assert.AreEqual(renkos[4].Open, 9.0);
            Assert.AreEqual(renkos[4].High, 9.0);
            Assert.AreEqual(renkos[4].Low, 8.0);
            Assert.AreEqual(renkos[4].Close, 8.0);
            Assert.AreEqual(renkos[4].Direction, BarDirection.Falling);
            Assert.AreEqual(renkos[4].Spread, 1.0);

            var openRenko = consolidator.OpenRenko();

            Assert.AreEqual(openRenko.Open, 8.0);
            Assert.AreEqual(openRenko.High, 8.0);
            Assert.AreEqual(openRenko.Low, 7.9);
            Assert.AreEqual(openRenko.Close, 7.9);
        }

        [TestCase(new double[] {1.38687, 1.38688, 1.38687, 1.38686, 1.38685, 1.38683,
                1.38682, 1.38682, 1.38684, 1.38682, 1.38682, 1.38680,
                1.38681, 1.38686, 1.38688, 1.38688, 1.38690, 1.38690,
                1.38691, 1.38692, 1.38694, 1.38695, 1.38697, 1.38697,
                1.38700, 1.38699, 1.38699, 1.38699, 1.38698, 1.38699,
                1.38697, 1.38698, 1.38698, 1.38697, 1.38698, 1.38698,
                1.38697, 1.38697, 1.38700, 1.38702, 1.38701, 1.38699,
                1.38697, 1.38698, 1.38696, 1.38698, 1.38697, 1.38695,
                1.38695, 1.38696, 1.38693, 1.38692, 1.38693, 1.38693,
                1.38692, 1.38693, 1.38692, 1.38690, 1.38686, 1.38685,
                1.38687, 1.38686, 1.38686, 1.38686, 1.38686, 1.38685,
                1.38684, 1.38678, 1.38679, 1.38680, 1.38680, 1.38681,
                1.38685, 1.38685, 1.38683, 1.38682, 1.38682, 1.38683,
                1.38682, 1.38683, 1.38682, 1.38681, 1.38680, 1.38681,
                1.38681, 1.38681, 1.38682, 1.38680, 1.38679, 1.38678,
                1.38675, 1.38678, 1.38678, 1.38678, 1.38682, 1.38681,
                1.38682, 1.38680, 1.38682, 1.38683, 1.38685, 1.38683,
                1.38683, 1.38684, 1.38683, 1.38683, 1.38684, 1.38685,
                1.38684, 1.38683, 1.38686, 1.38685, 1.38685, 1.38684,
                1.38685, 1.38682, 1.38684, 1.38683, 1.38682, 1.38683,
                1.38685, 1.38685, 1.38685, 1.38683, 1.38685, 1.38684,
                1.38686, 1.38693, 1.38695, 1.38693, 1.38694, 1.38693,
                1.38692, 1.38693, 1.38695, 1.38697, 1.38698, 1.38695,
                1.38696}, 0.0001)]
        [TestCase(new double[] {90.38687, 12.38688, 33.38687, 69.38686, 22.38685, 19.38683,
                19.38682, 51.38682, 12.38684, 41.38682, 47.38682, 30.38680,
                81.38681, 16.38686, 21.38688, 14.38688, 89.38690, 72.38690,
                71.38691, 71.38692, 3.38694, 71.38695, 50.38697, 97.38697,
                16.38700, 18.38699, 14.38699, 91.38699, 60.38698, 35.38699,
                51.38697, 91.38698, 41.38698, 21.38697, 44.38698, 35.38698,
                14.38697, 10.38697, 5.38700, 1.38702, 1.38701, 15.38699,
                31.38697, 11.38698, 16.38696, 21.38698, 16.38697, 19.38695,
                12.38695, 21.38696, 61.38693, 32.38692, 20.38693, 23.38693,
                11.38692, 13.38693, 7.38692, 16.38690, 30.38686, 34.38685,
                91.38687, 41.38686, 18.38686, 12.38686, 40.38686, 44.38685,
                18.38684, 15.38678, 81.38679, 19.38680, 32.38680, 37.38681,
                71.38685, 61.38685, 9.38683, 21.38682, 27.38682, 28.38683,
                16.38682, 17.38683, 0.38682, 81.38681, 60.38680, 65.38681,
                51.38681, 81.38681, 11.38682, 81.38680, 60.38679, 65.38678,
                41.38675, 19.38678, 11.38678, 51.38678, 25.38682, 30.38681,
                13.38682, 1.38680, 2.38682, 51.38683, 47.38685, 55.38683,
                21.38683, 11.38684, 13.38683, 81.38683, 70.38684, 75.38685,
                11.38684, 21.38683, 31.38686, 91.38685, 87.38685, 92.38684,
                10.38685, 13.38682, 4.38684, 21.38683, 29.38682, 34.38683,
                19.38685, 41.38685, 51.38685, 12.38683, 28.38685, 34.38684,
                81.38686, 15.38693, 15.38695, 1.38693, 8.38694, 13.38693,
                17.38692, 61.38693, 6.38695, 13.38697, 4.38698, 9.38695,
                61.38696}, 5)]
        public void ConsistentRenkos(double[] values, double barSize)
        {
            // Reproduce issue #5479
            // Test Renko bar consistency amongst three consolidators starting at different times

            var time = new DateTime(2016, 1, 1);
            var testValues = new List<decimal> (values.Select(x => (decimal)x));


            var consolidator1 = new RenkoConsolidator((decimal)barSize);
            var consolidator2 = new RenkoConsolidator((decimal)barSize);
            var consolidator3 = new RenkoConsolidator((decimal)barSize);

            // Update each of our consolidators starting at different indexes of test values
            for (int i = 0; i < testValues.Count; i++)
            {
                var data = new IndicatorDataPoint(time.AddSeconds(i), testValues[i]);
                consolidator1.Update(data);

                if (i > 10)
                {
                    consolidator2.Update(data);
                }

                if (i > 20)
                {
                    consolidator3.Update(data);
                }
            }

            // Assert that consolidator 2 and 3 price is the same as 1. Even though they started at different
            // indexes they should be the same
            var bar1 = consolidator1.Consolidated as RenkoBar;
            var bar2 = consolidator2.Consolidated as RenkoBar;
            var bar3 = consolidator3.Consolidated as RenkoBar;

            Assert.AreEqual(bar1.Close, bar2.Close);
            Assert.AreEqual(bar1.Close, bar3.Close);

            consolidator1.Dispose();
            consolidator2.Dispose();
            consolidator3.Dispose();
        }

        [TestCase(12.38684, 0.0001, 12.3868)]
        [TestCase(12.38686, 0.0001, 12.3869)]
        [TestCase(3.38694, 0.001, 3.387)]
        [TestCase(3.38644, 0.001, 3.386)]
        [TestCase(41.38698, 0.01, 41.39)]
        [TestCase(41.38498, 0.01, 41.38)]
        [TestCase(16.38696, 0.1, 16.4)]
        [TestCase(16.32696, 0.1, 16.3)]
        [TestCase(7.38692, 1, 7)]
        [TestCase(7.78692, 1, 8)]
        [TestCase(81.38679, 10, 80)]
        [TestCase(88.38679, 10, 90)]
        [TestCase(1247.38682, 100, 1200)]
        [TestCase(1257.38682, 100, 1300)]
        [TestCase(44500.2349, 1000, 45000)]
        [TestCase(44300.2349, 1000, 44000)]
        public void GetClosestMultipleWorksAsExpected(double price, double barSize, double expectedClosestMultiple)
        {
            Assert.AreEqual((decimal)expectedClosestMultiple, RenkoConsolidator.GetClosestMultiple((decimal)price, (decimal)barSize));
        }

        [TestCase(0)]
        [TestCase(-1)]
        public void GetClosestMultipleFailsWhenBarSizeIsLessThanZero(double barSize)
        {
            var message = Assert.Throws<ArgumentException>(() => RenkoConsolidator.GetClosestMultiple((decimal)34.78989, (decimal)barSize));
            Assert.AreEqual("BarSize must be strictly greater than zero", message.Message);
        }

        protected override IDataConsolidator CreateConsolidator()
        {
            return new TestRenkoConsolidator(1m);
        }

        protected override void AssertConsolidator(IDataConsolidator consolidator)
        {
            base.AssertConsolidator(consolidator);
            var renkoConsolidator = consolidator as TestRenkoConsolidator;
            var renkoBar = renkoConsolidator.OpenRenko();

            Assert.AreEqual(0, renkoBar.Open);
            Assert.AreEqual(0, renkoBar.Close);
            Assert.AreEqual(0, renkoBar.High);
            Assert.AreEqual(0, renkoBar.Low);
            Assert.AreEqual(default(DateTime), renkoBar.Start);
            Assert.AreEqual(default(DateTime), renkoBar.End);
        }

        protected override IEnumerable<IBaseData> GetTestValues()
        {
            var time = new DateTime(2016, 3, 1);
            return new List<IndicatorDataPoint>()
            {
                new IndicatorDataPoint(time, 10.0m),
                new IndicatorDataPoint(time.AddSeconds(1), 9.6m),
                new IndicatorDataPoint(time.AddSeconds(2), 10.5m),
                new IndicatorDataPoint(time.AddSeconds(3), 11.1m),
                new IndicatorDataPoint(time.AddSeconds(4), 11.0m),
                new IndicatorDataPoint(time.AddSeconds(5), 10.7m),
                new IndicatorDataPoint(time.AddSeconds(6), 11.6m),
                new IndicatorDataPoint(time.AddSeconds(7), 12.3m),
                new IndicatorDataPoint(time.AddSeconds(8), 12.3m),
                new IndicatorDataPoint(time.AddSeconds(9), 12.4m),
                new IndicatorDataPoint(time.AddSeconds(10), 11.5m),
                new IndicatorDataPoint(time.AddSeconds(11), 7.9m),
                new IndicatorDataPoint(time.AddSeconds(12), 7.9m)
            };
        }

        private class TestRenkoConsolidator : RenkoConsolidator
        {
            public TestRenkoConsolidator(decimal barSize)
                : base(barSize)
            {
            }

            public RenkoBar OpenRenko()
            {
                return new RenkoBar(null, OpenOn, CloseOn, BarSize, OpenRate, HighRate, LowRate, CloseRate);
            }
        }
    }
}
